package com.toe.forum.webapi.security.filter;

import com.alibaba.fastjson.JSON;
import com.toe.common.pojo.domain.ToeAuthenticationInfo;
import com.toe.common.restful.JsonResult;
import com.toe.common.restful.StateEnum;
import com.toe.common.utils.JwtTokenUtils;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>单点登录过滤器</p>
 */
@Component
@Slf4j
public class SSOFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenUtils jwtTokenUtils;

    @Value("${jwt.tokenHead}")
    private String jwtTokenHead;

    private static final String REQUEST_HEADER_AUTHORIZATION = "Authorization";

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        log.info("过滤器开始执行JwtAuthenticationFilter.doFilterInternal()");
        log.info("开始执行过滤器");
        //清空上下文,如果不清空
        //防止此前曾经存入过用户信息,后续即使没有jwt也会被放行
        SecurityContextHolder.clearContext();
        //客户端在提交请求时,必须在请求头的Authorization中添加JWT数据,这是当前服务器程序的规定,客户端应该遵守
        // 查看所有到达当前系统请求是否有认证信息，有则存放context的数据
        String authHeader = httpServletRequest.getHeader(REQUEST_HEADER_AUTHORIZATION);
        //判断是否存在jwt数据
        if (!StringUtils.hasText(authHeader)){
            //不存在则放行,后续有其他过滤器及相关组件进行处理,例如未登录则要求登录
            //此处不予直接阻止运行,因为登录,注册等请求本应该没有jwt
            System.out.println("请求头中没有jwt数据,当前过滤器放行");
            filterChain.doFilter(httpServletRequest,httpServletResponse); //表示放行,继续执行过滤器链中的其他成分
            return;  //必须
        }
        if (authHeader != null && authHeader.startsWith(jwtTokenHead)) {
            String authToken = authHeader.substring(jwtTokenHead.length());
            System.out.println("jwt中有数据,准备解析数据");
            try {
                //下面代码返回的是自定义认证框架ToeAuthenticationInfo的信息
                ToeAuthenticationInfo userInfo = jwtTokenUtils.getUserInfo(authToken);
                UsernamePasswordAuthenticationToken authentication=null;
                if (userInfo != null) {
                    //用户权限列表
                    //转换成Security支持的格式
                    List<String> authoritiesString=userInfo.getAuthorities();
                    List<GrantedAuthority> authorities=new ArrayList<>();
                    for (String authorityValue : authoritiesString) {
                        authorities.add(new SimpleGrantedAuthority(authorityValue));
                    }
                    //返回的UsernamePasswordAuthenticationToken中的principal就是用户名,credentials就是用户自定义认证信息,authorities里面就是权限列表
                    authentication= new UsernamePasswordAuthenticationToken(userInfo.getUsername(),userInfo,authorities);
                    //下一行代码是获取spring security的上下文,并将Authentication放到上下文中
                    //后续spring security发现上下文中含有Authentication时,就会视为已经登录,甚至可以获取相关信息
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                    //details属性存在于父类之中，主要描述两个信息，一个是remoteAddress 和sessionId。
                    //检验方法:this.getAuthenticationManager().authenticate(authRequest)
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
                    filterChain.doFilter(httpServletRequest,httpServletResponse);
                }
            }catch (ExpiredJwtException e){
                System.out.println("解析jwt失败,此jwt已过期"+e.getMessage());
                JsonResult<Void> jsonResult = JsonResult.failed(
                        StateEnum.ERR_JWT_EXPIRED,"解析jwt失败,此jwt已过期");
                //解析jsonResult
                String jsonString = JSON.toJSONString(jsonResult);
                //将错误信息返回到客户端
                System.out.println("响应结果:"+jsonString);
                httpServletResponse.setContentType("application/json; charset=utf-8");
                httpServletResponse.getWriter().println(jsonString);
                return;
            }catch (MalformedJwtException e){
                System.out.println("解析jwt失败,此jwt错误,无法解析"+e.getMessage());
                JsonResult<Void> jsonResult = JsonResult.failed(
                        StateEnum.ERR_JWT_MALFORMED,"解析jwt失败,此jwt错误,无法解析,谢符特,龟");
                //解析jsonResult
                String jsonString = JSON.toJSONString(jsonResult);
                //将错误信息返回到客户端
                System.out.println("响应结果:"+jsonString);
                httpServletResponse.setContentType("application/json; charset=utf-8");
                httpServletResponse.getWriter().println(jsonString);
                return;
            }catch (SignatureException e){
                System.out.println("解析jwt失败,此jwt签名错误"+e.getMessage());
                JsonResult<Void> jsonResult = JsonResult.failed(
                        StateEnum.ERR_JWT_SIGNATURE,"解析jwt失败,此jwt签名错误");
                //解析jsonResult
                String jsonString = JSON.toJSONString(jsonResult);
                //将错误信息返回到客户端
                System.out.println("响应结果:"+jsonString);
                httpServletResponse.setContentType("application/json; charset=utf-8");
                httpServletResponse.getWriter().println(jsonString);
                return;
            }catch (Exception e){
                System.out.println("解析jwt失败,异常类型:"+e.getClass().getName());
                e.printStackTrace();
                JsonResult<Void> jsonResult = JsonResult.failed(
                        StateEnum.ERR_SERVER_ERROR,"服务器正忙!");
                //解析jsonResult
                String jsonString = JSON.toJSONString(jsonResult);
                //将错误信息返回到客户端
                System.out.println("响应结果:"+jsonString);
                httpServletResponse.setContentType("application/json; charset=utf-8");
                httpServletResponse.getWriter().println(jsonString);
                return;
            }
        }
    }
}
